/**
 * Copyright (c) 2023 murenchao
 * taomu is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *       http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package cool.taomu.toolkit.git

import com.google.inject.Inject
import com.google.inject.name.Named
import cool.taomu.toolkit.git.entity.GitCommitInfoEntity
import cool.taomu.toolkit.git.entity.GitFileHistoryContentEntity
import cool.taomu.toolkit.utils.FileTools
import cool.taomu.toolkit.utils.inter.ICallback
import java.io.File
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.util.ArrayList
import java.util.HashMap
import java.util.List
import org.apache.commons.lang3.StringUtils
import org.apache.oro.text.perl.Perl5Util
import org.eclipse.jgit.api.AddCommand
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.ListBranchCommand.ListMode
import org.eclipse.jgit.api.MergeCommand
import org.eclipse.jgit.api.ResetCommand
import org.eclipse.jgit.api.RmCommand
import org.eclipse.jgit.api.Status
import org.eclipse.jgit.api.TransportCommand
import org.eclipse.jgit.api.TransportConfigCallback
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.ObjectLoader
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.RefSpec
import org.eclipse.jgit.treewalk.TreeWalk
import org.eclipse.xtend.lib.annotations.Accessors
import org.joda.time.DateTime
import org.slf4j.LoggerFactory

@Accessors
@Deprecated
class JGitTools {
	val static LOG = LoggerFactory.getLogger(JGitTools)

    @Inject(optional=true)
	TransportConfigCallback ssh

	@Inject(optional=true)
	CredentialsProvider cp;

	@Inject(optional=true)
	@Named("taomu.git.repository")
	String repository = "./repository";
	
	@Inject(optional=true)
	@Named("taomu.git.regex")
	String regex = "^(?:git@|ssh?:\\/\\/|https?:\\/\\/)(?:[a-zA-Z0-9\\-.]+\\.)+[a-zA-Z]{2,}(?::\\d+)?(?:\\/[a-zA-Z0-9\\-._\\/]+(?:\\.git)?(?:\\/[a-zA-Z0-9\\-._\\/]*)?)?$";

	boolean isAutoCommit = true;
	String remoteRepository = "";
	String branch = "master"

	def setRepository(String repository) {
		this.setRepository("", repository)
	}
	def getPath(){
		return this.repository;
	}
	def setRepository(String basePath, String repository) {
		var p5 = new Perl5Util();
		LOG.info("匹配的正则表达式为:{}",regex);
		if (p5.match(#["/", regex, "/"].join(""), repository.trim)) {
			this.remoteRepository = repository.trim;
			this.repository = repository.trim.substring(repository.trim.lastIndexOf("/") + 1, repository.trim.lastIndexOf(".git")).trim;
		} else {
			this.repository = repository.trim;
		}
		if (StringUtils.isNotBlank(basePath.trim)) {
			this.repository = #[basePath.trim, this.repository].join(File.separator).trim
		}
		LOG.info("GIT远程仓库地址:{}", this.remoteRepository);
		LOG.info("GIT本地仓库地址:{}", this.repository);
	}

	def static void main(String[] args) {
		var r = new JGitTools();
		r.setRepository = "http://example.com/path/to/repo.git";
	}

	private def void credentials(TransportCommand<?, ?> command) {
		if (ssh !== null) {
			command.transportConfigCallback = ssh;
		} else if (cp !== null) {
			command.credentialsProvider = cp;
		} else {
			LOG.info("GIT登陆credentials是空的");
		}
	}

	private def getRepository(String remoteRepository) {
		var projectName = remoteRepository;
		if (remoteRepository.contains("/")) {
			projectName = remoteRepository.substring(remoteRepository.lastIndexOf("/") + 1)
		}
		var file = new File(#[repository, projectName].join(File.separator));
		if (!file.exists) {
			LOG.info("创建目录:{}", file.absolutePath);
			file.mkdirs;
		}
		return file;
	}

	def isReprositoryExists() {
		var file = new File(#[repository.trim, ".git"].join(File.separator));
		file.exists;
	}

	def init() {
		var file = new File(#[repository.trim, ".git"].join(File.separator));
		var createRepo = FileRepositoryBuilder.create(file);
		if (createRepo !== null) {
			createRepo.create();
		}
		createRepo.close();
		LOG.info("初始化本地仓库:{}", file.absolutePath);
	}

	def rmAndCommit(String msg, ICallback<RmCommand> call) {
		if (call !== null) {
			try {
				var file = new File(#[repository.trim, ".git"].join(File.separator))
				LOG.info("打开GIT仓库{}", file.absolutePath);
				val git = Git.open(file)
				val rm = git.rm();
				call.call(rm);
				rm.call();
				if (isAutoCommit) {
					var commit = git.commit.setMessage(StringUtils.defaultIfBlank(msg, "无信息")).call();
					LOG.info("删除提交的id:{}", commit.getName);
				}
				git.close;
			} catch (Exception ex) {
				ex.printStackTrace
			}

		}
	}

	def addAndCommit(String msg, ICallback<AddCommand> call) {
		if (call !== null) {
			try {
				var file = new File(#[repository.trim, ".git"].join(File.separator))
				LOG.info("打开GIT仓库{}", file.absolutePath);
				val git = Git.open(file)
				val add = git.add();
				call.call(add);
				add.call();
				if (isAutoCommit) {
					var commit = git.commit.setMessage(StringUtils.defaultIfBlank(msg, "无信息")).call();
					LOG.info("添加提交的id:{}", commit.getName);
				}
				git.close;
			} catch (Exception ex) {
				ex.printStackTrace
			}

		}
	}

	def getCommitIds(Integer num, ICallback<GitCommitInfoEntity> call) {
		try {
			LOG.info("获取commit历史");
			val git = Git.open(new File(#[repository.trim, ".git"].join(File.separator)))
			val RevWalk walk = new RevWalk(git.getRepository());
			var RevCommit head = walk.parseCommit(git.getRepository().resolve("HEAD"));
			walk.reset();
			walk.markStart(head);
			var walkList = walk.toList
			LOG.info("获取到{}条commit id", walkList.size());
			if (num !== null && call !== null) {
				var size = Math.min(num, walkList.size());
				walkList.subList(0, size).forEach [
					var entity = new GitCommitInfoEntity();
					entity.id = it.getName;
					entity.time = new DateTime(it.committerIdent.when);
					LOG.info("commit id:{}", entity.toString());
					call.call(entity);
				]
			} else if (call !== null) {
				walkList.forEach [
					var entity = new GitCommitInfoEntity();
					entity.id = it.getName;
					entity.time = new DateTime(it.committerIdent.when);
					LOG.info("commit id:{}", id);
					call.call(entity);
				]
			}
			walk.dispose
			git.close();
		} catch (IOException e) {
			e.printStackTrace
		}
	}

	def retrieveFileContent(String filePath, List<String> commitIds, ICallback<GitFileHistoryContentEntity> call) {
		try {
			val git = Git.open(new File(#[repository.trim, ".git"].join(File.separator)))
			val RevWalk walk = new RevWalk(git.getRepository());
			if (StringUtils.isNotBlank(filePath)) {
				commitIds.forEach [ commitId |
					var RevCommit commit = walk.parseCommit(git.getRepository().resolve(commitId));
					var fpath = filePath.replace(repository.trim + File.separator, "").replace(File.separator, "/");
					handleCommit(git, commitId, commit, fpath, walk, call);
				]
			} else {
				FileTools.scannerFile(repository, [
					if (it.file && !it.absolutePath.contains(".git")) {
						commitIds.forEach [ commitId |
							var RevCommit commit = walk.parseCommit(git.getRepository().resolve(commitId));
							var fpath = it.absolutePath.replace(repository.trim + File.separator, "").replace(
								File.separator, "/");
							handleCommit(git, commitId, commit, fpath, walk, call);
						]
					}
				])
			}
			walk.dispose;
			git.close;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private def void handleCommit(Git git, String commitId, RevCommit commit, String filePath, RevWalk walk,
		ICallback<GitFileHistoryContentEntity> call) {
		try (var TreeWalk treeWalk = TreeWalk.forPath(git.getRepository(), filePath, commit.getTree())) {
			if (treeWalk !== null) {
				var ObjectId objectId = treeWalk.getObjectId(0); // 文件在第一个索引位置  
				var ObjectLoader loader = git.getRepository().open(objectId);
				var byte[] data = loader.getBytes();
				var String content = new String(data, StandardCharsets.UTF_8);
				LOG.info("从{}提交中获取{}文件内容", commit.getName(), filePath);
				var gfhc = new GitFileHistoryContentEntity();
				gfhc.filePath = filePath;
				gfhc.content = content;
				gfhc.time = new DateTime(commit.committerIdent.when);
				gfhc.commitId = commitId;
				call.call(gfhc);
			} else {
				LOG.info("从{}提交没有发现文件{},提取结束", commit.getName(), filePath);
			}
		}
	}

	def status(ICallback<Status> call) {
		LOG.info("获取{}仓库的状态信息", repository);
		var file = new File(#[repository.trim, ".git"].join(File.separator));
		try(val git = Git.open(file)) {
			var status = git.status.call();
			call.call(status);
		}
	}

	def Clone() {
		LOG.info("克隆远程仓库{},克隆分支或标签{}", remoteRepository, branch);
		var file = new File(this.repository);
		file.mkdirs;
		var clone = Git.cloneRepository.URI = this.remoteRepository;
		clone.bare = false;
		clone.cloneAllBranches = true;
		clone.branch = this.branch;
		clone.directory = file;
		this.credentials(clone);
		try (var git = clone.call()) {
			var refs = git.lsRemote.setHeads(true).setRemote(remoteRepository);
			this.credentials(refs);
			var refs2 = refs.call();
			var StringBuffer s = new StringBuffer();
			for (ref : refs2) {
				s.append(ref.getName());
				s.append("\n");
			}
			s.append("clone success")
			LOG.info("克隆完成");
			return s.toString();
		}
	}

	def pull() {
		LOG.info("从仓库 {} pull => {}", this.remoteRepository, this.branch);
		var file = new File(#[repository.trim, ".git"].join(File.separator));
		try(val git = Git.open(file)) {
			var pull = git.pull.remoteBranchName = this.branch;
			this.credentials(pull);
			pull.call;
		}
	}

	def remoteBranchList() {
		var tempDir = Files.createTempDirectory("jgit-temp-dir");
		val blist = new ArrayList<String>();
		try(var git = Git.init().setDirectory(tempDir.toFile).call()) {
			var cmd = git.lsRemote().remote = this.remoteRepository;
			this.credentials(cmd);
			var refs = cmd.call;
			refs.forEach [ ref |
				if (ref.getName.startsWith("refs/heads/")) {
					var String branchName = ref.name.substring("refs/heads/".length());
					blist.add(branchName);
				}
			]
		} finally {
			FileTools.fileDelete(tempDir)
		}
		return blist;
	}

	def checkout(String name) {
		LOG.info("checkout:{}", name);
		var file = new File(#[repository.trim, ".git"].join(File.separator));
		try(val git = Git.open(file)) {
			var ck = git.checkout.name = name;
			ck.call;
		}
		LOG.info("checkout完成");
	}

	def HEAD() {
		var file = new File(#[repository.trim, ".git"].join(File.separator));
		try(var repository = new FileRepositoryBuilder()  
                .setGitDir(file)  
                .readEnvironment()  
                .findGitDir()  
                .build()) {
			var headId = repository.resolve("HEAD");
			return headId.name();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// TODO 旧代码
	def clone(String remoteRepository, boolean isAllBranches, String branchOrTag) {
		LOG.info("克隆远程仓库{},是否克隆全部分支{},克隆分支或标签{}", remoteRepository, isAllBranches ? "是" : "否", branchOrTag);
		var file = getRepository(remoteRepository);
		file.mkdirs;
		var clone = Git.cloneRepository().setURI(remoteRepository).setBare(false)
		if (isAllBranches) {
			clone.setCloneAllBranches = isAllBranches;
		} else {
			clone.cloneAllBranches = isAllBranches;
			clone.branch = branchOrTag;
		}
		clone.setDirectory(file);
		this.credentials(clone);
		try (var git = clone.call()) {
			var refs = git.lsRemote.setHeads(true).setRemote(remoteRepository).call();
			var StringBuffer s = new StringBuffer();
			for (ref : refs) {
				s.append(ref.getName());
				s.append("\n");
			}
			s.append("clone success")
			LOG.info("克隆完成");
			return s.toString();
		}
	}

	def push(String remoteRepository, String name, String branch) {
		var file = getRepository(remoteRepository);
		try(var git = Git.open(file)) {
			var push = git.push.setRefSpecs(new RefSpec(branch)).setRemote(name);
			this.credentials(push);
			var infos = push.call()
			var buffer = new StringBuffer();
			for (info : infos) {
				buffer.append(info.messages);
				buffer.append("\n")
			}
			return buffer.toString();
		}
	}

	def fetch(String remoteRepository, String branch) {
		var file = getRepository(remoteRepository);
		// fetch 一个git库
		var fetchCommand = Git.open(file).fetch().setRemote(remoteRepository).setRefSpecs(
			#[new RefSpec(branch + ":" + branch)])
		this.credentials(fetchCommand);
		var result = fetchCommand.call();
		return result.getMessages();
	}

	def fetchAll(String remoteRepository) {
		LOG.info("fetch仓库{}全部分支", remoteRepository);
		var file = getRepository(remoteRepository);
		var refs = Git.lsRemoteRepository().setHeads(true).setRemote(remoteRepository).call();
		var log = new StringBuffer("fetch:\n");
		for (ref : refs) {
			var String refStr = ref.getName();
			var fetchCommand = Git.open(file).fetch().setRemote(remoteRepository).setRefSpecs(
				#[new RefSpec(refStr + ":" + refStr)])
			this.credentials(fetchCommand);
			var result = fetchCommand.call();
			log.append(result.getMessages());
			log.append("\n");
		}
		log.append("Fetch success")
		LOG.info("fetch完成");
		return log.toString();
	}

	def commit(String remoteRepository, String[] files, String msg) {
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			val addc = git.add();
			files.forEach [
				addc.addFilepattern(it).call();
			]
			git.commit.setMessage(msg).call();
		}
	}

	def status(String remoteRepository) {
		var map = new HashMap<String, String>();
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			var status = git.status.call();
			map.put("Added", status.added.toString());
			map.put("Changed", status.changed.toString());
			map.put("Conflicting", status.conflicting.toString());
			map.put("ConflictingStageState", status.conflictingStageState.toString());
			map.put("IgnoredNotInIndex", status.ignoredNotInIndex.toString());
			map.put("Missing", status.missing.toString());
			map.put("Modified", status.modified.toString());
			map.put("Removed", status.removed.toString());
			map.put("UntrackedFiles", status.untracked.toString());
			map.put("UntrackedFolders", status.untrackedFolders.toString());
			return map;
		}
	}

	def branchCreate(String remoteRepository, String name) {
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			git.branchCreate.setName(name).call();
		}
	}

	def branchDelete(String remoteRepository, String[] names) {
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			git.branchDelete.setBranchNames(names).call();
		}
	}

	def branchRename(String remoteRepository, String oldName, String newName) {
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			git.branchRename.setNewName(newName).setOldName(oldName).call;
		}
	}

	def branchList(String remoteRepository) {
		return branchList(remoteRepository, null);
	}

	def branchList(String remoteRepository, ListMode mode) {
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			val result = newArrayList;
			val blist = git.branchList;
			if (mode !== null) {
				blist.listMode = mode;
			}
			blist.call.forEach [
				result.add(it.name);
			]
			return result;
		}
	}

	private def isBranchExist(Git git, String bname) {
		var refs = git.branchList().call();
		for (Ref ref : refs) {
			if (ref.name.contains(bname)) {
				return true;
			}
		}
		return false;
	}

	def checkoutBranch(String remoteRepository, String name) {
		LOG.info("从仓库 {} checkout 分支:{}", remoteRepository, name);
		var repo = new FileRepositoryBuilder().setGitDir(new File(remoteRepository)).build();
		try(var git = new Git(repo)) {
			if (isBranchExist(git, name)) {
				git.checkout().setCreateBranch(false).setName(name).call();
			} else {
				git.checkout().setCreateBranch(true).setName(name).setStartPoint("origin" + name).call();
			}
			git.pull().call();
		}
		LOG.info("checkout完成");
	}

	def checkout(String remoteRepository, String name) {
		LOG.info("从仓库 {} checkout:{}", remoteRepository, name);
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			var ck = git.checkout.name = name;
			ck.call;
		}
		LOG.info("checkout完成");
	}

	def merge(String remoteRepository, String srcName, String mergeBranch, String mgs) {
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			var ref = git.checkout.setName(srcName).call();
			git.checkout.setName(mergeBranch).call();
			var merge = git.merge.include(ref).setCommit(true).fastForward = MergeCommand.FastForwardMode.NO_FF
			merge.message = mgs;
			merge.call;
		}
	}

	def pull(String remoteRepository, String remoteBranch) {
		LOG.info("从仓库 {} pull => {}", remoteRepository, remoteBranch);
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			var pull = git.pull.remoteBranchName = remoteBranch;
			this.credentials(pull);
			pull.call;
		}
	}

	def reset(String remoteRepository, String objectId) {
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			var walk = new RevWalk(git.repository);
			var objId = git.repository.resolve(objectId);
			var revCommit = walk.parseCommit(objId);
			var vision = revCommit.getParent(0).getName();
			git.reset.setMode(ResetCommand.ResetType.HARD).setRef(vision).call;
		}
	}

	def revert(String remoteRepository, String commitId) {
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			var walk = new RevWalk(git.repository);
			var revCommit = walk.parseCommit(ObjectId.fromString(commitId));
			git.revert.include(revCommit).call();
		}
	}

	def String logOne(String remoteRepository) {
		LOG.info("从仓库 {} 获取第一条日志", remoteRepository);
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			var log = git.log.call();
			var RevCommit revCommit = log.get(0);
			var sb = new StringBuilder();
			sb.append(revCommit.getName()).append("\n");
			sb.append(revCommit.committerIdent.name).append(":");
			sb.append(revCommit.committerIdent.emailAddress).append("\n");
			sb.append(revCommit.committerIdent.when).append("\n");
			sb.append(revCommit.fullMessage).append("\n");
			return sb.toString;
		}
	}

	def List<String> logAll(String remoteRepository) {
		LOG.info("从仓库 {} 获取所有日志", remoteRepository);
		var file = getRepository(remoteRepository);
		try(val git = Git.open(file)) {
			var log = git.log.call();
			var logs = new ArrayList<String>();
			for (RevCommit revCommit : log) {
				var sb = new StringBuilder();
				sb.append(revCommit.getName()).append("\n");
				sb.append(revCommit.committerIdent.name).append(":");
				sb.append(revCommit.committerIdent.emailAddress).append("\n");
				sb.append(revCommit.committerIdent.when).append("\n");
				sb.append(revCommit.fullMessage).append("\n");
				logs.add(sb.toString());
			}
			return logs;
		}
	}

	def setAutoCommit(boolean b) {
		this.isAutoCommit = b;
	}
}
